<!DOCTYPE html>
<meta charset=utf-8>
<title>Tests for discrete animation</title>
<link rel="help" href="https://drafts.csswg.org/web-animations/#play-state">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../external/wpt/web-animations/testcommon.js"></script>
<body>
<script>
'use strict';

function createIdleAnimation(t) {
  var animation = createDiv(t).animate([], 100000);
  animation.cancel();
  return animation;
}

function createRunningAnimation(t) {
  var animation = createIdleAnimation(t);
  animation.play();
  animation.startTime = document.timeline.currentTime;
  return animation;
}

function createPendingStartTimeAnimation(t) {
  var animation = createIdleAnimation(t);
  animation.play();
  return animation;
}

function createPausedAnimation(t) {
  var animation = createIdleAnimation(t);
  animation.pause();
  animation.currentTime = 0;
  return animation;
}

function createFinishedAnimation(t) {
  var animation = createIdleAnimation(t);
  animation.play();
  animation.finish();
  return animation;
}

// Initial animation states
test(function(t) {
  var animation = createIdleAnimation(t);
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Play state is idle after canceling an animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_equals(animation.playState, 'running');
}, "Play state is running after playing a canceled animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_times_equal(animation.currentTime, 0);
  assert_equals(animation.playState, 'running');
}, "Play state is running after playing and setting start time of a canceled animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  assert_equals(animation.startTime, null);
  assert_times_equal(animation.currentTime, 0);
  assert_equals(animation.playState, 'paused');
}, "Play state is paused after pausing and setting current time of a canceled animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_equals(animation.playState, 'finished');
}, "Play state is finished after playing and finishing a cancelled animation");

// Changed animation states
test(function(t) {
  var animation = createIdleAnimation(t);
  animation.play();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.pause();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.cancel();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.currentTime = 1000;
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Setting currentTime on an idle animation");

test(function(t) {
  var animation = createIdleAnimation(t);
  animation.startTime = document.timeline.currentTime - 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on an idle animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.play();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.pause();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.cancel();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.currentTime = 1000;
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 1000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a pending starttime animation");

test(function(t) {
  var animation = createPendingStartTimeAnimation(t);
  animation.startTime = document.timeline.currentTime - 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a pending starttime animation");

promise_test(async (t) => {
  var animation = createRunningAnimation(t);
  assert_false(animation.pending);
  // Advance the animation from the start boundary. Otherwise the test is
  // prone to flaking. The initial value of current time is 0 +/- epsilon. If
  // negative, the hold time is reset and a pending play is triggered, clamped
  // to the starting boundary per spec. Conversely, if the animation is playing
  // and current time is within bounds, then play is a no-op.
  await waitForAnimationFrames(1);
  var startTime = animation.startTime;
  var currentTime = animation.currentTime;
  animation.play();
  assert_times_equal(animation.startTime, startTime);
  assert_times_equal(animation.currentTime, currentTime);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting play() on a running animation");

async_test(function(t) {
  var animation = createRunningAnimation(t);
  animation.pause();
  assert_not_equals(animation.startTime, null);
  assert_times_equal(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.startTime, null);
    assert_greater_than_equal(animation.currentTime, 0);
    assert_false(animation.pending);
  }));
}, "Setting pause() on a running animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  animation.cancel();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Setting cancel() on a running animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Setting finish() on a running animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  // Ensure the current time is precisely zero. We create a non-pending running
  // animation by explicitly setting the start time to the timeline time. This
  // is insufficient to ensure that we are precisely on the starting boundary
  // due to floating point precision errors. If the resulting value of current
  // time is slightly greater than zero, rather than snapping to the end point,
  // the animation will simply reverse direction per spec.
  animation.currentTime = 0;
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_times_equal(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting reverse() on a running animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  animation.currentTime = 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a running animation");

test(function(t) {
  var animation = createRunningAnimation(t);
  animation.startTime = document.timeline.currentTime - 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a running animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.play();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  // Calling pause on an animation that is already paused or pause-pending is a
  // no-op.
  animation.pause();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'paused');
}, "Calling pause() on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.cancel();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.currentTime = 1000;
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 1000);
  assert_equals(animation.playState, 'paused');
}, "Setting currentTime on a paused animation");

test(function(t) {
  var animation = createPausedAnimation(t);
  animation.startTime = document.timeline.currentTime - 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - 1000);
  assert_times_equal(animation.currentTime, 1000);
  assert_equals(animation.playState, 'running');
}, "Setting startTime on a paused animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.play();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 0);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling play() on a finished animation");

async_test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.pause();
  assert_not_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'paused');
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.startTime, null);
    assert_equals(animation.currentTime, 100000);
    assert_false(animation.pending);
  }));
}, "Calling pause() on a finished animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.cancel();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, null);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'idle');
}, "Calling cancel() on a finished animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.finish();
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 100000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'finished');
}, "Calling finish() on a finished animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.reverse();
  assert_equals(animation.startTime, null);
  assert_equals(animation.currentTime, 100000);
  assert_true(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Calling reverse() on a finished animation");

test(function(t) {
  var animation = createFinishedAnimation(t);
  animation.currentTime = 1000;
  assert_times_equal(animation.startTime, document.timeline.currentTime - animation.currentTime);
  assert_equals(animation.currentTime, 1000);
  assert_false(animation.pending);
  assert_equals(animation.playState, 'running');
}, "Setting currentTime on a finished animation");

async_test(function(t) {
  var animation = createIdleAnimation(t);
  animation.play();
  var animationCurrentTime = animation.currentTime;
  var timelineCurrentTime = document.timeline.currentTime;
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.playState, 'running');
    assert_time_greater_than_equal(animation.startTime,
                                   timelineCurrentTime);
    assert_time_greater_than_equal(animation.currentTime,
                                   animationCurrentTime);
  }));
}, "PlayState is 'running' while playing a cancelled animation");

async_test(function(t) {
  let animation = createRunningAnimation(t);
  animation.pause();
  let animationCurrentTime = animation.currentTime;
  animation.ready.then(t.step_func_done(() => {
    assert_equals(animation.playState, 'paused');
    assert_equals(animation.startTime, null);
    assert_time_greater_than_equal(animation.currentTime,
                                   animationCurrentTime);
  }));
}, "PlayState is 'running' while pausing a running animation");
</script>
